﻿using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Optional
{
    public struct Option<T> : IEquatable<Option<T>>, IEquatable<T>
    {
        private readonly T _value;

        public Option(T value)
        {
            _value = value;
            IsSome = true;
        }

        public T Value => IsSome ? _value : throw new InvalidOperationException("Can not read value of None");

        public bool IsSome {get;}

        public bool IsNone => !IsSome;

        public T Or(T @default) => IsSome ? _value : @default;

        public T Or(Func<T> defaultLambda) => IsSome ? _value : defaultLambda();

        public ValueTask<T> Or(Func<Task<T>> asyncDefault) => IsSome ? new ValueTask<T>(_value) : new ValueTask<T>(asyncDefault());

        public Option<object> Do(Action<T> doWithSome)
        {
            if(IsNone)
                return Option.None;
            doWithSome(_value);
            return new object();
        }

        public async Task<Option<object>> Do(Func<T, Task> doWithSome)
        {
            if(IsNone)
                return Option.None;
            await doWithSome(_value);
            return new object();
        }

        public Option<TResult> Do<TResult>(Func<T, TResult> doWithSome)
        {
            if(IsNone)
                return Option.None;
            return doWithSome(_value);
        }

        public async Task<Option<TResult>> Do<TResult>(Func<T, Task<TResult>> doWithSome)
        {
            if(IsNone)
                return Option.None;
            return await doWithSome(_value);
        }

        public Option<object> ElseDo(Action doIfNone)
        {
            if(IsSome)
                return Option.None;
            doIfNone();
            return new object();
        }

        public async Task<Option<object>> ElseDo(Func<Task> doIfNone)
        {
            if(IsSome)
                return Option.None;
            await doIfNone();
            return new object();
        }

        public Option<TResult> ElseDo<TResult>(Func<TResult> doIfNone)
        {
            if(IsSome)
                return Option.None;
            return doIfNone();
        }

        public async Task<Option<TResult>> ElseDo<TResult>(Func<Task<TResult>> doIfNone)
        {
            if(IsSome)
                return Option.None;
            return await doIfNone();
        }

        public override int GetHashCode() => HashCode.Combine(_value, IsSome);

        public override string ToString() => IsSome ? $"Some({_value})" : "None";

        public override bool Equals(object? obj)
        {
            return obj switch
            {
                Option<T> other => Equals(other),
                T value => Equals(value),
                _ => false
            };
        }

        public bool Equals(T other)
        {
            return this switch
            {
                _ when IsSome => EqualityComparer<T>.Default.Equals(other, _value),
                _ => false,
            };
        }

        public bool Equals(Option<T> other)
        {
            return other switch
            {
                _ when this.IsNone && other.IsNone => true,
                _ when this.IsSome != other.IsSome => false,
                _ => EqualityComparer<T>.Default.Equals(other.Value, _value)
            };
        }

        public static implicit operator Option<T>(T value) => new Option<T>(value);

        public static implicit operator Option<T>(Option none) => new Option<T>();

        public static bool operator==(Option<T> left, Option<T> right)
        {
            return left.Equals(right);
        }

        public static bool operator!=(Option<T> left, Option<T> right)
        {
            return !(left == right);
        }

        public static bool operator==(Option<T> left, T right)
        {
            return left.Equals(right);
        }

        public static bool operator!=(Option<T> left, T right)
        {
            return !(left == right);
        }
    }
}
